package cn.luowb.checkchat.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.luowb.checkchat.common.context.UserContext;
import cn.luowb.checkchat.common.exception.ClientException;
import cn.luowb.checkchat.common.exception.ServiceException;
import cn.luowb.checkchat.common.util.RedisKeyUtil;
import cn.luowb.checkchat.dao.entity.*;
import cn.luowb.checkchat.dao.mapper.*;
import cn.luowb.checkchat.dto.req.PostFeedDTO;
import cn.luowb.checkchat.dto.req.PostReqDTO;
import cn.luowb.checkchat.dto.resp.PostFeedRespDTO;
import cn.luowb.checkchat.dto.resp.PostRespDTO;
import cn.luowb.checkchat.dto.resp.UserRespDTO;
import cn.luowb.checkchat.service.MediaService;
import cn.luowb.checkchat.service.PostService;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author heimenkyou
 * @description 针对表【post(帖子)】的数据库操作Service实现
 * @createDate 2025-09-01 19:07:09
 */

@Slf4j
@Service
@RequiredArgsConstructor
public class PostServiceImpl extends ServiceImpl<PostMapper, PostDO>
        implements PostService {

    private final MediaService mediaService;
    private final PostMapper postMapper;
    private final UserMapper userMapper;
    private final CategoryMapper categoryMapper;
    private final MediaResourceMapper mediaResourceMapper;
    private final PostCollectionMapper postCollectionMapper;
    private final LikeMapper likeMapper;

    private final RedisKeyUtil redisKeyUtil;

    @Autowired
    private StringRedisTemplate redisTemplate;

    // 在类的字段部分添加
    private DefaultRedisScript<List> deletePostCacheScript;

    @PostConstruct
    public void init() {
        deletePostCacheScript = new DefaultRedisScript<>();
        deletePostCacheScript.setLocation(new ClassPathResource("DELETE_POST_CACHE.lua"));
        deletePostCacheScript.setResultType(List.class);
    }


    @Override
    @Transactional
    public Long createPost(PostReqDTO postReqDTO) {

        // 增加一个帖子
        PostDO postDO = new PostDO();

        // 填入userId
        // todo: 在前后端联调的时候打开
        Long userId = UserContext.getUserId();

        // 鲁邦性检验：如果用户没有登录，不能创建帖子
        if (userId == null) {
            throw new ServiceException("用户未登录");
        }

        postDO.setUid(userId);
        postDO.setCateId(postReqDTO.getCateId());
        postDO.setTitle(postReqDTO.getTitle());
        postDO.setContent(postReqDTO.getContent());

        // 初始化操作
        postDO.setLikeCount(0);
        postDO.setReadCount(0);
        // todo(已完成): 新增收藏数量
        postDO.setCollectionCount(0);
        postDO.setCommentCount(0);

        postDO.setPostTop(0);
        postDO.setStatus(1);
        postDO.setCreateTime(LocalDateTime.now());
        postDO.setUpdateTime(LocalDateTime.now());

        postMapper.insert(postDO);

        // todo（已完成）: 增加帖子图片，此时前端已经将资源的url传递过来了，我们直接把他保存在数据库的media表中
        ArrayList<String> mediaUrlList = postReqDTO.getMedia();
        if (mediaUrlList != null && mediaUrlList.size() > 0) {
            for (String mediaUrl : mediaUrlList) {
                MediaResourceDO mediaResourceDO = new MediaResourceDO();
                mediaResourceDO.setType("image");
                mediaResourceDO.setRefId(postDO.getId());
                mediaResourceDO.setRefType("post");
                mediaResourceDO.setUrl(mediaUrl);
                mediaResourceDO.setCreateTime(LocalDateTime.now());
                mediaResourceDO.setUpdateTime(LocalDateTime.now());
                mediaResourceMapper.insert(mediaResourceDO);
            }
        }

        return postDO.getId();
    }

    @Override
    public PostRespDTO getPostDetailById(Integer id) {

        String postInfoKey = redisKeyUtil.buildPostInfoKey(id);

        // 尝试从 Redis 中获取帖子基本信息
        String postDOJson = (String) redisTemplate.opsForValue().get(postInfoKey);

        PostDO postDO;

        // 如果 Redis 中没有，则查询数据库
        if (postDOJson == null) {
            // 根据id查询帖子
            postDO = postMapper.selectById(id);
            // 如果帖子不存在
            if (postDO == null) {
                throw new ServiceException("帖子不存在");
            }
            // 根据帖子的status判断是否能查询帖子
            if (postDO.getStatus() == 0) {
                throw new ServiceException("帖子已违规，禁止查询");
            }
            // 先把阅读数量加一
            postDO.setReadCount(postDO.getReadCount() + 1);
            // 再把阅读数量更新到数据库
            postMapper.updateById(postDO);

            postDOJson = JSON.toJSONString(postDO);
            // 将帖子基本信息缓存到 Redis，设置过期时间（例如 1 小时）
            redisTemplate.opsForValue().set(postInfoKey, postDOJson, Duration.ofHours(1));
        } else {
            // 如果从 Redis 中获取到了数据，先把数据变成postDO
            postDO = JSON.parseObject(postDOJson, PostDO.class);
            // 更新阅读数量
            postDO.setReadCount(postDO.getReadCount() + 1);
            // 更新数据库中的阅读数量
            postMapper.updateById(postDO);
            // 更新 Redis 缓存
            String newJson = JSON.toJSONString(postDO);
            redisTemplate.opsForValue().set(postInfoKey, newJson, Duration.ofHours(1));
        }

        // 将postDO转换为postRespDTO
        PostRespDTO postRespDTO = new PostRespDTO();
        BeanUtil.copyProperties(postDO, postRespDTO);

        // 补充剩下字段

        // 补充cateName
        String cateName = categoryMapper.getCateName(postRespDTO.getCateId());
        postRespDTO.setCateName(cateName);

        // 补充media
        ArrayList<String> mediaUrlList = mediaResourceMapper.getMediaUrlListByPostId(id);
        postRespDTO.setMedia(mediaUrlList);

        // 补充用户信息
        UserDO userDO = userMapper.selectById(postDO.getUid());
        UserRespDTO userRespDTO = new UserRespDTO();
        BeanUtil.copyProperties(userDO, userRespDTO);
        postRespDTO.setUser(userRespDTO);

        // 根据用户的id判断用户是否可以对该帖子进行编辑和删除
        Long userId = UserContext.getUserId();

        // 如果用户未登录，不能对帖子进行编辑和删除
        if (userId == null) {
            postRespDTO.setCanEdit(false);
            postRespDTO.setCanDelete(false);
            return postRespDTO;
        }

        // 到这里的用户都是已经登录的用户了
        // todo: 补充是否被本用户收藏，以及是否被本用户点赞
        // 判断是否被本用户收藏

        QueryWrapper<PostCollectionDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", userId);
        queryWrapper.eq("post_id", id);
        PostCollectionDO postCollectionDO = postCollectionMapper.selectOne(queryWrapper);
        if (postCollectionDO != null) {
            postRespDTO.setCollected(true);
        }

        // todo：判断是否被本用户点赞，逻辑完成之后直接打开即可使用
        QueryWrapper<LikeDO> queryWrapper2 = new QueryWrapper<>();
        queryWrapper2.eq("uid", userId);
        queryWrapper2.eq("obj_id", id);
        queryWrapper2.eq("type", 0);
        LikeDO postLikeDO = likeMapper.selectOne(queryWrapper2);
        if (postLikeDO != null) {
            postRespDTO.setLiked(true);
        }

        // 如果用户id和帖子的用户id相等，说明是该用户发布的帖子，用户可以对该帖子进行编辑和删除
        if (userId.equals(postDO.getUid())) {
            postRespDTO.setCanEdit(true);
            postRespDTO.setCanDelete(true);
        }

        return postRespDTO;
    }

    @Override
    @Transactional
    public void deletePost(Integer id) {
        // 根据id查询帖子
        PostDO postDO = postMapper.selectById(id);

        // 如果帖子不存在
        if (postDO == null) {
            throw new ClientException("帖子不存在");
        }

        // 根据用户id判断能不能删除帖子
        Long userId = UserContext.getUserId();

        // 如果用户未登录，不能删除帖子
        if (userId == null) {
            throw new ClientException("用户未登录");
        }

        // 如果用户id和帖子的用户id不相等，说明不是该用户发布的帖子，用户不能删除帖子
        if (!userId.equals(postDO.getUid())) {
            throw new ClientException("用户没有权限删除该帖子");
        }

        // 删除帖子
        postMapper.deleteById(id);
        // 删除帖子对应的图片
        mediaResourceMapper.delete(
                Wrappers.<MediaResourceDO>lambdaQuery()
                        .eq(MediaResourceDO::getRefId, id)
                        .eq(MediaResourceDO::getRefType, "post")
        );
        // 删除帖子对应的收藏信息
        postCollectionMapper.delete(
                Wrappers.<PostCollectionDO>lambdaQuery()
                        .eq(PostCollectionDO::getPostId, id)
        );
        // 删除帖子对应的点赞信息
        likeMapper.delete(
                Wrappers.<LikeDO>lambdaQuery()
                        .eq(LikeDO::getObjId, id)
                        .eq(LikeDO::getType, 0)
        );
        // 删除对应的缓存信息
        String postInfoKey = redisKeyUtil.buildPostInfoKey(id);
        String likeSetKey = redisKeyUtil.buildLikeSetKey(0, id);
        String likeCountKey = redisKeyUtil.buildLikeCountKey(0, id);
//        redisTemplate.delete(likeCountKey);
//        redisTemplate.delete(likeSetKey);
        //lua脚本版
        redisTemplate.execute(deletePostCacheScript, Arrays.asList(postInfoKey, likeSetKey, likeCountKey));

    }

    @Override
    public PostFeedRespDTO getFeedStreamPost(PostFeedDTO requestParam) {
        // 获取帖子列表
        List<PostDO> postList = getPostList(requestParam);
        List<Long> postIdList = postList.stream().map(PostDO::getId).toList();

        // 批量获取附加信息
        Map<Long, String> cateNameMap = getCateNameMap(postList);
        Map<Long, List<String>> mediaUrlMap = getMediaUrlMap(postIdList);
        Map<Long, UserRespDTO> userMap = getUserMap(postList);
        Set<Long> collectedPostIdSet = getCollectedPostIds(postIdList); // 收藏帖子id集合
        Set<Long> likedPostIdSet = getLikedPostIds(postIdList); // 点赞帖子id集合

        // 组装返回结果
        return buildResponse(requestParam, postList, cateNameMap, mediaUrlMap,
                userMap, collectedPostIdSet, likedPostIdSet);
    }

    // 辅助方法: 查询非置顶帖子
    private List<PostDO> queryNormalPosts(long limit, Long lastId, PostFeedDTO postFeedDTO) {
        // 查询条件
        LambdaQueryWrapper<PostDO> queryWrapper = Wrappers.lambdaQuery(PostDO.class)
                .eq(PostDO::getStatus, 1) // 只查询状态为1的帖子（正常状态）
                .eq(PostDO::getPostTop, 0) // 非置顶帖子
                .eq(Objects.nonNull(postFeedDTO.getCateId()) && postFeedDTO.getCateId() > 0, PostDO::getCateId,
                        postFeedDTO.getCateId()) // 分类条件
                .eq(Objects.nonNull(postFeedDTO.getUid()) && postFeedDTO.getUid() > 0, PostDO::getUid,
                        postFeedDTO.getUid()) // 用户条件
                .lt(Objects.nonNull(lastId), PostDO::getId, lastId) // 如果有lastId，查询比lastId小的数据
                .orderByDesc(PostDO::getId) // 排序规则：按id降序
                .last("LIMIT " + limit); // 限制查询数量

        return postMapper.selectList(queryWrapper);
    }

    // 辅助方法: 构建最终响应
    @NotNull
    private PostFeedRespDTO buildResponse(PostFeedDTO requestParam, List<PostDO> postList,
                                          Map<Long, String> cateNameMap,
                                          Map<Long, List<String>> mediaUrlMap, Map<Long, UserRespDTO> userMap,
                                          Set<Long> collectedPostIdSet, Set<Long> likedPostIdSet) {
        Long currentUserId = UserContext.getUserId();
        List<PostRespDTO> postRespDTOList = new ArrayList<>();
        for (PostDO postDO : postList) {
            PostRespDTO dto = BeanUtil.toBean(postDO, PostRespDTO.class);
            dto.setCateName(cateNameMap.get(postDO.getCateId()));
            dto.setMedia(mediaUrlMap.get(postDO.getId()));
            dto.setUser(userMap.get(postDO.getUid()));

            // 编辑和删除权限
            boolean isOwner = Objects.equals(postDO.getUid(), currentUserId);
            dto.setCanDelete(isOwner);
            dto.setCanEdit(isOwner);

            dto.setCollected(collectedPostIdSet.contains(postDO.getId()));
            dto.setLiked(likedPostIdSet.contains(postDO.getId()));

            postRespDTOList.add(dto);
        }

        // 构建返回结果
        PostFeedRespDTO respDTO = new PostFeedRespDTO();
        respDTO.setPostRespDTOList(postRespDTOList);
        if (!postRespDTOList.isEmpty()) {
            // 最后一个帖子的id
            respDTO.setLastId(postRespDTOList.get(postRespDTOList.size() - 1).getId());
        }

        // 补充符合条件的总条数
        // 构建LambdaQueryWrapper，使用链式调用和条件判断减少if语句
        LambdaQueryWrapper<PostDO> totalQueryWrapper = Wrappers.<PostDO>lambdaQuery()
                .eq(Objects.nonNull(requestParam.getCateId()) && requestParam.getCateId() > 0, PostDO::getCateId,
                        requestParam.getCateId()) // 分类条件
                .eq(Objects.nonNull(requestParam.getUid()) && requestParam.getUid() > 0, PostDO::getUid,
                        requestParam.getUid()); // 用户条件
        respDTO.setTotal(postMapper.selectCount(totalQueryWrapper));

        return respDTO;
    }

    // 辅助方法: 获取用户点赞状态
    @NotNull
    private Set<Long> getLikedPostIds(List<Long> postIdList) {
        Long currentUserId = UserContext.getUserId();
        if (currentUserId == null || CollectionUtil.isEmpty(postIdList)) {
            return Collections.emptySet();
        }
        List<LikeDO> likeList = likeMapper.selectList(
                Wrappers.<LikeDO>lambdaQuery()
                        .eq(LikeDO::getUid, currentUserId)
                        .eq(LikeDO::getType, 0) // 0-帖子点赞
                        .in(LikeDO::getObjId, postIdList));
        return likeList.stream().map(LikeDO::getObjId).collect(Collectors.toSet());
    }

    // 辅助方法: 获取用户收藏状态
    @NotNull
    private Set<Long> getCollectedPostIds(List<Long> postIdList) {
        Long currentUserId = UserContext.getUserId();
        if (currentUserId == null || CollectionUtil.isEmpty(postIdList)) {
            return Collections.emptySet();
        }

        List<PostCollectionDO> collectionList = postCollectionMapper.selectList(
                Wrappers.<PostCollectionDO>lambdaQuery()
                        .eq(PostCollectionDO::getUid, currentUserId)
                        .in(PostCollectionDO::getPostId, postIdList));
        return collectionList.stream().map(PostCollectionDO::getPostId).collect(Collectors.toSet());
    }

    // 辅助方法: 获取用户信息映射
    @NotNull
    private Map<Long, UserRespDTO> getUserMap(List<PostDO> postList) {
        List<Long> uidList = postList.stream().map(PostDO::getUid).distinct().toList();
        List<UserDO> userDOList = CollectionUtil.isEmpty(uidList) ? Collections.emptyList()
                : userMapper.selectList(
                Wrappers.<UserDO>lambdaQuery()
                        .in(UserDO::getUid, uidList));
        return userDOList.stream()
                .collect(Collectors.toMap(UserDO::getUid, userDO -> BeanUtil.toBean(userDO, UserRespDTO.class)));
    }

    // 辅助方法: 获取媒体URL映射
    @NotNull
    private Map<Long, List<String>> getMediaUrlMap(List<Long> postIdList) {
        List<MediaResourceDO> mediaList = CollectionUtil.isEmpty(postIdList) ? Collections.emptyList()
                : mediaResourceMapper.selectList(
                Wrappers.<MediaResourceDO>lambdaQuery()
                        .eq(MediaResourceDO::getRefType, "post")
                        .in(MediaResourceDO::getRefId, postIdList));
        Map<Long, List<String>> mediaUrlMap = mediaList.stream()
                // 媒体资源分组：refId → List<url>
                .collect(Collectors.groupingBy(
                        MediaResourceDO::getRefId, // 分组 key：帖子ID
                        Collectors.mapping( // 对每组内的元素做转换
                                MediaResourceDO::getUrl, // 转换为 url 字符串
                                Collectors.toList() // 收集成 List
                        )));
        return mediaUrlMap;
    }

    // 辅助方法: 获取分类名称映射
    @NotNull
    private Map<Long, String> getCateNameMap(List<PostDO> postList) {
        List<Long> cateIdList = postList.stream().map(PostDO::getCateId).distinct().toList();
        Map<Long, String> cateNameMap = CollectionUtil.isEmpty(cateIdList) ? Collections.emptyMap()
                : categoryMapper.selectList(
                        Wrappers.<CategoryDO>lambdaQuery()
                                .in(CategoryDO::getCateId, cateIdList))
                .stream()
                .collect(Collectors.toMap(CategoryDO::getCateId, CategoryDO::getCateName));
        return cateNameMap;
    }

    // 辅助方法: 获取帖子列表
    @NotNull
    private List<PostDO> getPostList(PostFeedDTO requestParam) {
        if (requestParam.getLastId() == null) {
            // 首次查询：先查所有置顶帖子
            return getFirstPagePosts(requestParam);
        } else {
            // 非首次查询：直接查询比LastId小的普通帖子
            return queryNormalPosts(requestParam.getSize(), requestParam.getLastId(), requestParam);
        }
    }

    // 辅助方法: 获取第一页帖子(包含置顶逻辑)
    @NotNull
    private List<PostDO> getFirstPagePosts(PostFeedDTO requestParam) {
        // 查询指定帖子
        LambdaQueryWrapper<PostDO> topPostQueryWrapper = Wrappers.lambdaQuery(PostDO.class)
                .eq(PostDO::getStatus, 1) // 只查询状态为1的帖子（正常状态）
                .eq(PostDO::getPostTop, 1) // 置顶帖子
                .eq(Objects.nonNull(requestParam.getCateId()) && requestParam.getCateId() > 0, PostDO::getCateId,
                        requestParam.getCateId()) // 分类条件
                .eq(Objects.nonNull(requestParam.getUid()) && requestParam.getUid() > 0, PostDO::getUid,
                        requestParam.getUid()) // 用户条件
                .orderByDesc(PostDO::getId); // 排序规则：按id降序
        List<PostDO> topPostList = postMapper.selectList(topPostQueryWrapper);

        // 根据置顶数量决定补充策略
        List<PostDO> normalPostList;
        if (topPostList.size() < requestParam.getSize()) {
            // 置顶数量不够，用普通帖子补够剩下的数量
            normalPostList = queryNormalPosts(requestParam.getSize() - topPostList.size(), null, requestParam);
        } else {
            // 置顶数量多了，全部返回置顶帖子，然后再额外查一条普通帖
            normalPostList = queryNormalPosts(1, null, requestParam);
        }
        List<PostDO> postList = new ArrayList<>(topPostList);
        postList.addAll(normalPostList);
        return postList;
    }
}
